// (c) 2007 Messaging Logic. All Rights Reserved.
package org.xi8ix.loader;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.ArrayList;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * A context loader allowing any class to be booted without having any dependencies
 * on the servlet API. Simply add a context listener to the web.xml for the war bundle
 * with this context listener, and provide a context parameter
 * <tt>org.xi8ix.loader.boot.class</tt> set to the fully qualified name(s) of the class to load.
 * If more than one boot class should be created, separate the names with a comma.
 *
 * <h3>Simple life cycle</h3>
 *
 * <p>All listed classes will be instantiated in order. Once all boot classes are instantiated,
 * thy are optionally initialized by invoking a no-arg method named "init".
 * Classes without an init() method will be ignored (does not cause errors). Finally, when
 * the plugin is destroyed, an optional, no-arg "destroy" method on the boot class(es) will be invoked if
 * available.
 *
 * @author Iain Shigeoka
 */
public class ContextListener implements ServletContextListener {

    private static final Logger LOG = Logger.getLogger(ContextListener.class.getName());

    private ArrayList<Object> bootObjects = new ArrayList<Object>();

    public void contextInitialized(ServletContextEvent event) {
        String bootClassList = event.getServletContext().getInitParameter("org.xi8ix.loader.boot.class");
        if (bootClassList == null || bootClassList.trim().length() == 0) {
            LOG.log(Level.WARNING,"Could not locate plugin boot class for plugin {0}",event.getServletContext().getContextPath());
        } else {
            for (String bootClass: bootClassList.split(",")) {
                try {
                    bootClass = bootClass.trim();
                    if (bootClass.length() == 0) {
                        LOG.log(Level.FINE,"Empty class name in init parameter {0}",bootClassList);
                    } else {
                        LOG.log(Level.FINE, "Loading boot class {0}", bootClass);
                        ClassLoader loader = Thread.currentThread().getContextClassLoader();
                        if (loader == null) {
                            loader = this.getClass().getClassLoader();
                            LOG.log(Level.FINEST,"Using class loader {0}", loader);
                        } else {
                            LOG.log(Level.FINEST,"Using thread context class loader {0},{1}",new Object[]{Thread.currentThread(),loader});
                        }
                        Object boot = loader.loadClass(bootClass).newInstance();
                        bootObjects.add(boot);
                        LOG.log(Level.FINE, "Loaded boot class {0}", boot);
                    }
                } catch (Exception e) {
                    LOG.log(Level.WARNING, "Could not load plugin boot class", e);
                }
            }
        }
        LOG.log(Level.FINEST,"Initializing {0} boot classes",bootObjects.size());
        for (Object boot: bootObjects) {
            LOG.log(Level.FINEST,"Intializing {0}",boot);
            try {
                boolean initialized = false;
                for (Method method: boot.getClass().getMethods()) {
                    if ("init".equals(method.getName()) && method.getParameterTypes().length == 0) {
                        method.invoke(boot);
                        initialized = true;
                        break;
                    }
                }
                if (initialized) {
                    LOG.log(Level.FINE, "Initialized boot class: {0}", boot);
                } else {
                    LOG.log(Level.FINE, "No init method found on boot class: {0}", boot);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    public void contextDestroyed(ServletContextEvent event) {
        LOG.log(Level.FINEST,"Destroying {0} boot classes",bootObjects.size());
        for (Object boot: bootObjects) {
            try {
                boolean destroyed = false;
                for (Method method: boot.getClass().getMethods()) {
                    if ("destroy".equals(method.getName()) && method.getParameterTypes().length == 0) {
                        method.invoke(boot);
                        destroyed = true;
                        break;
                    }
                }
                if (destroyed) {
                    LOG.log(Level.FINE, "Destroyed boot class: {0}", boot);
                } else {
                    LOG.log(Level.FINE, "No destroy method found on boot class: {0}", boot);
                }
            } catch (IllegalAccessException e) {
                LOG.log(Level.FINE, "Could not destroy boot class: access problem",e);
            } catch (InvocationTargetException e) {
                LOG.log(Level.FINE, "Could not destroy boot class: invocation target problem",e);
            }
        }
    }
}
